/*******************************************************************************
 * Copyright (c) 2009 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package org.luaj.vm2;

import org.luaj.vm2.compiler.DumpState;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;


/**
 * Class to undump compiled lua bytecode into a {@link Prototype} instances.
 * <p>
 * The {@link LoadState} class implements {@link Globals#Undumper}
 * which is used to undump a string of bytes that represent a lua binary file
 * using either the C-based lua compiler, or luaj's {@link DumpState#dump} function.
 * <p>
 * The canonical method to load and execute code is done
 * indirectly using the Globals:
 * <pre> {@code
 * Globals globals = JsePlatform.standardGlobals();
 * LuaValue chunk = globasl.load("print('hello, world')", "main.lua");
 * chunk.call();
 * } </pre>
 * This should work regardless of which {@link Globals.Compiler}
 * has been installed.
 * <p>
 * By default, when using {@link JsePlatform} or {@JmePlatform}
 * to construct globals, the {@link LoadState} is installed
 * as the default {@link Globals#undumper}.
 * <p>
 * <p>
 * A lua binary file is created via {@link DumpState#dump}:
 * <pre> {@code
 * Globals globals = JsePlatform.standardGlobals();
 * Prototype p = globals.compilePrototype(new StringReader("print('hello, world')"), "main.lua");
 * ByteArrayOutputStream o = new ByteArrayOutputStream();
 * DumpState.dump(p, o, false);
 * byte[] lua_binary_file_bytes = o.toByteArray();
 * } </pre>
 * <p>
 * The {@link LoadState} may be used directly to undump these bytes:
 * <pre> {@code
 * Prototypep = LoadState.instance.undump(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua");
 * LuaClosure c = new LuaClosure(p, globals);
 * c.call();
 * } </pre>
 * <p>
 * <p>
 * More commonly, the {@link Globals#undumper} may be used to undump them:
 * <pre> {@code
 * Prototype p = globals.loadPrototype(new ByteArrayInputStream(lua_binary_file_bytes), "main.lua", "b");
 * LuaClosure c = new LuaClosure(p, globals);
 * c.call();
 * } </pre>
 *
 * @see LuaCompiler
 * @see LuaClosure
 * @see LuaFunction
 * @see LoadState#compiler
 * @see LoadState#load(InputStream, String, LuaValue)
 * @see LuaC
 * @see LuaJC
 */
public class LoadState implements Globals.Undumper {

    /**
     * Shared instance of Globals.Undumper to use loading prototypes from binary lua files
     */
    public static Globals.Undumper instance = new LoadState();

    /**
     * format corresponding to non-number-patched lua, all numbers are floats or doubles
     */
    public static final int NUMBER_FORMAT_FLOATS_OR_DOUBLES = 0;

    /**
     * format corresponding to non-number-patched lua, all numbers are ints
     */
    public static final int NUMBER_FORMAT_INTS_ONLY = 1;

    /**
     * format corresponding to number-patched lua, all numbers are 32-bit (4 byte) ints
     */
    public static final int NUMBER_FORMAT_NUM_PATCH_INT32 = 4;

    // type constants
    public static final int LUA_TINT = (-2);
    //    public static int LUA_TNONE = (-1);remove by yanqiu
    public static final int LUA_TNIL = 0;
    public static final int LUA_TBOOLEAN = 1;
    //    public static int LUA_TLIGHTUSERDATA = 2;remove by yanqiu
    public static final int LUA_TNUMBER = 3;
    public static final int LUA_TSTRING = 4;
//    public static int LUA_TTABLE = 5;remove by yanqiu
//    public static int LUA_TFUNCTION = 6;remove by yanqiu
//    public static int LUA_TUSERDATA = 7;remove by yanqiu
//    public static int LUA_TTHREAD = 8;remove by yanqiu
//    public static int LUA_TVALUE = 9;remove by yanqiu

    /**
     * The character encoding to use for file encoding.  Null means the default encoding
     */
    public static String encoding = null;

    /**
     * Signature byte indicating the file is a compiled binary chunk
     */
    public static byte[] LUA_SIGNATURE = {'\033', 'L', 'u', 'a'};

    /**
     * Data to catch conversion errors
     */
    public static byte[] LUAC_TAIL = {(byte) 0x19, (byte) 0x93, '\r', '\n', (byte) 0x1a, '\n',};


    /**
     * Name for compiled chunks
     */
    public static String SOURCE_BINARY_STRING = "binary string";


    /**
     * for header of binary files -- this is Lua 5.2
     */
    public static int LUAC_VERSION = 0x52;

    /**
     * for header of binary files -- this is the official format
     */
    public static int LUAC_FORMAT = 0;

    /**
     * size of header of binary files
     */
//	public static  int LUAC_HEADERSIZE		= 12; remove by yanqiu

    // values read from the header
    private int luacVersion;
    private int luacFormat;
    private boolean luacLittleEndian;
    private int luacSizeofInt;
    private int luacSizeofSizeT;
    private int luacSizeofInstruction;
    private int luacSizeofLuaNumber;
    private int luacNumberFormat;

    /**
     * input stream from which we are loading
     */
    public DataInputStream is;

    /**
     * Name of what is being loaded?
     */
    String name;

    private static LuaValue[] NOVALUES = {};
    private static Prototype[] NOPROTOS = {};
    private static LocVars[] NOLOCVARS = {};
    //	private static  LuaString[]  NOSTRVALUES = {}; remove by yanqiu
    private static Upvaldesc[] NOUPVALDESCS = {};
    private static int[] NOINTS = {};

    /**
     * Read buffer
     */
    private byte[] buf = new byte[512];

    /**
     * Install this class as the standard Globals.Undumper for the supplied Globals
     */
    public static void install(Globals globals) {
        globals.undumper = instance;
    }

    /**
     * Load a 4-byte int value from the input stream
     *
     * @return the int value laoded.
     **/
    int loadInt() throws IOException {
        is.readFully(buf, 0, 4);
        return luacLittleEndian ?
                (buf[3] << 24) | ((0xff & buf[2]) << 16) | ((0xff & buf[1]) << 8) | (0xff & buf[0]) :
                (buf[0] << 24) | ((0xff & buf[1]) << 16) | ((0xff & buf[2]) << 8) | (0xff & buf[3]);
    }

    /**
     * Load an array of int values from the input stream
     *
     * @return the array of int values laoded.
     **/
    int[] loadIntArray() throws IOException {
        int n = loadInt();
        if (n == 0)
            return NOINTS;

        // read all data at once
        int m = n << 2;
        if (buf.length < m)
            buf = new byte[m];
        is.readFully(buf, 0, m);
        int[] array = new int[n];
        for (int i = 0, j = 0; i < n; ++i, j += 4)
            array[i] = luacLittleEndian ?
                    (buf[j + 3] << 24) | ((0xff & buf[j + 2]) << 16) | ((0xff & buf[j + 1]) << 8) | (0xff & buf[j + 0]) :
                    (buf[j + 0] << 24) | ((0xff & buf[j + 1]) << 16) | ((0xff & buf[j + 2]) << 8) | (0xff & buf[j + 3]);

        return array;
    }

    /**
     * Load a long  value from the input stream
     *
     * @return the long value laoded.
     **/
    long loadInt64() throws IOException {
        int a, b;
        if (this.luacLittleEndian) {
            a = loadInt();
            b = loadInt();
        } else {
            b = loadInt();
            a = loadInt();
        }
        return (((long) b) << 32) | (((long) a) & 0xffffffffL);
    }

    /**
     * Load a lua strin gvalue from the input stream
     *
     * @return the {@link LuaString} value laoded.
     **/
    LuaString loadString() throws IOException {
        int size = this.luacSizeofSizeT == 8 ? (int) loadInt64() : loadInt();
        if (size == 0)
            return null;
        byte[] bytes = new byte[size];
        is.readFully(bytes, 0, size);
        return LuaString.valueOf(bytes, 0, bytes.length - 1);
    }

    /**
     * Convert bits in a long value to a {@link LuaValue}.
     *
     * @param bits long value containing the bits
     * @return {@link LuaInteger} or {@link LuaDouble} whose value corresponds to the bits provided.
     */
    public static LuaValue longBitsToLuaNumber(long bits) {
        if ((bits & ((1L << 63) - 1)) == 0L) {
            return LuaValue.ZERO;
        }

        int e = (int) ((bits >> 52) & 0x7ffL) - 1023;

        if (e >= 0 && e < 31) {
            long f = bits & 0xFFFFFFFFFFFFFL;
            int shift = 52 - e;
            long intPrecMask = (1L << shift) - 1;
            if ((f & intPrecMask) == 0) {
                int intValue = (int) (f >> shift) | (1 << e);
                return LuaInteger.valueOf(((bits >> 63) != 0) ? -intValue : intValue);
            }
        }

        return LuaValue.valueOf(Double.longBitsToDouble(bits));
    }

    /**
     * Load a number from a binary chunk
     *
     * @return the {@link LuaValue} loaded
     * @throws IOException if an i/o exception occurs
     */
    LuaValue loadNumber() throws IOException {
        if (luacNumberFormat == NUMBER_FORMAT_INTS_ONLY) {
            return LuaInteger.valueOf(loadInt());
        } else {
            return longBitsToLuaNumber(loadInt64());
        }
    }

    /**
     * Load a list of constants from a binary chunk
     *
     * @param f the function prototype
     * @throws IOException if an i/o exception occurs
     */
    void loadConstants(Prototype f) throws IOException {
        int n = loadInt();
        LuaValue[] values = n > 0 ? new LuaValue[n] : NOVALUES;
        for (int i = 0; i < n; i++) {
            switch (is.readByte()) {
                case LUA_TNIL:
                    values[i] = LuaValue.NIL;
                    break;
                case LUA_TBOOLEAN:
                    values[i] = (0 != is.readUnsignedByte() ? LuaValue.TRUE : LuaValue.FALSE);
                    break;
                case LUA_TINT:
                    values[i] = LuaInteger.valueOf(loadInt());
                    break;
                case LUA_TNUMBER:
                    values[i] = loadNumber();
                    break;
                case LUA_TSTRING:
                    values[i] = loadString();
                    break;
                default:
                    throw new IllegalStateException("bad constant");
            }
        }
        f.k = values;

        n = loadInt();
        Prototype[] protos = n > 0 ? new Prototype[n] : NOPROTOS;
        for (int i = 0; i < n; i++)
            protos[i] = loadFunction(f.source);
        f.p = protos;
    }


    void loadUpvalues(Prototype f) throws IOException {
        int n = loadInt();
        f.upvalues = n > 0 ? new Upvaldesc[n] : NOUPVALDESCS;
        for (int i = 0; i < n; i++) {
            boolean instack = is.readByte() != 0;
            int idx = ((int) is.readByte()) & 0xff;
            f.upvalues[i] = new Upvaldesc(null, instack, idx);
        }
    }

    /**
     * Load the debug info for a function prototype
     *
     * @param f the function Prototype
     * @throws IOException if there is an i/o exception
     */
    void loadDebug(Prototype f) throws IOException {
        f.source = loadString();
        f.lineinfo = loadIntArray();
        int n = loadInt();
        f.locvars = n > 0 ? new LocVars[n] : NOLOCVARS;
        for (int i = 0; i < n; i++) {
            LuaString varname = loadString();
            int startpc = loadInt();
            int endpc = loadInt();
            f.locvars[i] = new LocVars(varname, startpc, endpc);
        }

        n = loadInt();
        for (int i = 0; i < n; i++)
            f.upvalues[i].name = loadString();
    }

    /**
     * Load a function prototype from the input stream
     *
     * @param p name of the source
     * @return {@link Prototype} instance that was loaded
     * @throws IOException
     */
    public Prototype loadFunction(LuaString p) throws IOException {
        Prototype f = new Prototype();
////		this.L.push(f);
//		f.source = loadString();
//		if ( f.source == null )
//			f.source = p;
        f.linedefined = loadInt();
        f.lastlinedefined = loadInt();
        f.numparams = is.readUnsignedByte();
        f.is_vararg = is.readUnsignedByte();
        f.maxstacksize = is.readUnsignedByte();
        f.code = loadIntArray();
        loadConstants(f);
        loadUpvalues(f);
        loadDebug(f);

        // TODO: add check here, for debugging purposes, I believe
        // see ldebug.c
//		 IF (!luaG_checkcode(f), "bad code");

//		 this.L.pop();
        return f;
    }

    /**
     * Load the lua chunk header values.
     *
     * @throws IOException if an i/o exception occurs.
     */
    public void loadHeader() throws IOException {
        luacVersion = is.readByte();
        luacFormat = is.readByte();
        luacLittleEndian = (0 != is.readByte());
        luacSizeofInt = is.readByte();
        luacSizeofSizeT = is.readByte();
        luacSizeofInstruction = is.readByte();
        luacSizeofLuaNumber = is.readByte();
        luacNumberFormat = is.readByte();
        for (int i = 0; i < LUAC_TAIL.length; ++i)
            if (is.readByte() != LUAC_TAIL[i])
                throw new LuaError("Unexpeted byte in luac tail of header, index=" + i);
    }

    /**
     * Load input stream as a lua binary chunk if the first 4 bytes are the lua binary signature.
     *
     * @param stream InputStream to read, after having read the first byte already
     * @return {@link Prototype} that was loaded, or null if the first 4 bytes were not the lua signature.
     * @throws IOException if an IOException occurs
     */
    public Prototype undump(InputStream stream, String chunkname) throws IOException {
        // check rest of signature
        if (stream.read() != LUA_SIGNATURE[0]
                || stream.read() != LUA_SIGNATURE[1]
                || stream.read() != LUA_SIGNATURE[2]
                || stream.read() != LUA_SIGNATURE[3])
            return null;

        // load file as a compiled chunk
        String sname = getSourceName(chunkname);
        LoadState s = new LoadState(stream, sname);
        s.loadHeader();

        // check format
        switch (s.luacNumberFormat) {
            case NUMBER_FORMAT_FLOATS_OR_DOUBLES:
            case NUMBER_FORMAT_INTS_ONLY:
            case NUMBER_FORMAT_NUM_PATCH_INT32:
                break;
            default:
                throw new LuaError("unsupported int size");
        }
        return s.loadFunction(LuaString.valueOf(sname));
    }

    /**
     * Construct a source name from a supplied chunk name
     *
     * @param name String name that appears in the chunk
     * @return source file name
     */
    public static String getSourceName(String name) {
        String sname = name;
        if (name.startsWith("@") || name.startsWith("="))
            sname = name.substring(1);
        else if (name.startsWith("\033"))
            sname = SOURCE_BINARY_STRING;
        return sname;
    }

    /**
     * Private constructor for create a load state
     */
    private LoadState(InputStream stream, String name) {
        this.name = name;
        this.is = new DataInputStream(stream);
    }

    private LoadState() {
        this.name = "";
        this.is = null;
    }
}
